package buildinjava.java;

import buildinjava.io.Urls;
import java.io.File;
import java.net.URL;

/**
 * Class helper functions.
 * 
 * @author Gili Tzabari
 */
public final class JavaClass
{
	/**
	 * Prevent construction.
	 */
	private JavaClass()
	{
	}

	/**
	 * Returns a class' binary name.
	 *
	 * @param className the class name
	 * @param loader the class loader
	 * @return null if the class could not be found
	 */
	public static String getBinaryName(String className, ClassLoader loader)
	{
		//TODO: review
		
		// ClassLoaders are only required to define Packages immediately before loading nested classes:
		// http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html#definePackage%28java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.net.URL%29
		//
		// As such we cannot rely upon ClassLoader.getResource(packageName) to indicate whether a
		// package exists. Instead, we must use ClassLoader.getResource(className) and assume that
		// success indicates a class exists whereas failure indicates a yet-to-be-defined or
		// non-existent package. There is no way to differentiate between the two.
		StringBuilder result = new StringBuilder(className.replace(".", "/") + ".class");
		while (true)
		{
			if (loader.getResource(result.toString()) != null)
				return result.toString();
			int index = result.lastIndexOf("/");
			if (index == -1)
				break;
			result.replace(index, index + 1, "$");
		}
		return null;
	}

	/**
	 * Returns the path of the source code file associated with a class file.
	 *
	 * @param classFile the path of the class file
	 * @return the path of the source code file
	 */
	public static File getSourceName(File classFile)
	{
		String path = classFile.getPath();
		int index = path.indexOf("$");
		if (index != -1)
		{
			path = path.substring(0, index);
			return new File(path + ".java");
		}
		index = path.indexOf(".class");
		if (index == -1)
			throw new IllegalArgumentException("binaryName is not a class: " + classFile);
		return new File(path.substring(0, index) + ".java");
	}

	/**
	 * Returns a class file from a class name.
	 *
	 * @param className the class name
	 * @param loader the class loader
	 * @return null if the class could not be found
	 */
	public static File getClassFile(String className, ClassLoader loader)
	{
		//TODO: review
		
		// ClassLoaders are only required to define Packages immediately before loading nested classes:
		// http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html#definePackage%28java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.net.URL%29
		//
		// As such we cannot rely upon ClassLoader.getResource(packageName) to indicate whether a
		// package exists. Instead, we must use ClassLoader.getResource(className) and assume that
		// success indicates a class exists whereas failure indicates a yet-to-be-defined or
		// non-existent package. There is no way to differentiate between the two.
		StringBuilder result = new StringBuilder(className.replace(".", "/") + ".class");
		while (true)
		{
			URL url = loader.getResource(result.toString());
			if (url != null)
				return Urls.toFile().apply(url);
			int index = result.lastIndexOf("/");
			if (index == -1)
				break;
			result.replace(index, index + 1, "$");
		}
		return null;
	}

	/**
	 * Returns the path of the default package associated with a class.
	 *
	 * @param className the class binary name
	 * @param loader the class loader
	 * @return null if the class could not be found
	 */
	public static File getDefaultPackage(String className, ClassLoader loader)
	{
		File result = getClassFile(className, loader);
		for (int i = 0; i < className.split("\\.").length; ++i)
			result = result.getParentFile();
		return result;
	}
}
